Skip to content

fix(vcs): stop Check out here from discarding local pull request work - #15036

Merged
juliusmarminge merged 14 commits into
pingdotgg:mainfrom
saphid:upstream-pr-checkout-keep-local-work
Oct 20, 2026
Merged

juliusmarminge merged 14 commits into
pingdotgg:mainfrom
saphid:upstream-pr-checkout-keep-local-work

Conversation

@saphid

@saphid saphid commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Local pull-request checkout could discard tracked edits or unpublished commits, then report success even when the selected branch was not at the pull request head.

Local checkout now refuses staged or unstaged tracked edits before calling the provider, uses an unforced checkout, and only fast-forwards through the existing lossless refresh. Ahead and divergent commits are preserved. The result is checked against the provider head, and both web entry points show the same warning when that head cannot be confirmed. A canonical checkout-root lease serializes the local checkout with competing branch workflows in this server process. Worktree checkout retains its existing behavior.

Maintainer takeover of @saphid's #15036; original commits and author credit are preserved, with main merged rather than rewritten. The rebuild consolidates the lease in GitWorkflowService, removes duplicated GitManager checkout/root handling and the redundant symbolic-ref guard, and reuses the normalized Azure record type.

Verification

  • Focused suite reduced from 269 to 262 cases across nine existing files. All 250 main-baseline cases remain; PR-added cases reduced from 19 to 12. Three real-Git concurrency cases replace seven overlapping cases; the callback-wiring toast test was removed.
  • Regression checks against main fail for tracked edits, divergence, switch-only refresh, ahead branches and unavailable heads. Removing canonicalization fails all three concurrency cases. Restored implementation passes 262/262, one worker.
  • Current head: targeted lint and format over all 23 changed TypeScript files; sequential scoped typechecks and exports Knip for server, web, GitLab, Azure DevOps, Bitbucket and Forgejo; whole-diff whitespace check all pass. No repo-wide checks.
  • Fresh complete-diff round-3 reviews by Claude Opus 5.5 and GPT-6-Sol found no confirmed correctness, data-loss or compatibility blocker at a9a827897e, after merging main 382bf4d906. Earlier Sol review also found no blocker; initial Opus startup attempts failed before the final successful review.
  • Desktop shares the web checkout UI. Mobile currently requests worktree mode. Contracts and connection-mode transport behavior are unchanged. Agent provider adapters are unaffected.

Client proof

Captured with the isolated Linux web app at a9a827897e, fresh worktree-owned state and a disposable clone. No agent turn was launched.

  • Local checkout through the branch picker refused a tracked README edit. The README blob hash and original checkout HEAD were unchanged.
  • After clearing that disposable edit, a branch one unpublished commit ahead of the public PR head was checked out without losing the commit. The client displayed the off-head warning, and Git independently confirmed the exact unpublished commit remained HEAD.
  • The PR-detail checkout action was attempted, but GitHub returned an API rate-limit error before checkout. That entry point, clean first checkout and a matched visual baseline remain unverified in the client. The real-Git integration and mutation tests above cover those checkout behaviors; these are after screenshots, not a fabricated before/after pair.

After: tracked edits are refused

After: local checkout refuses tracked changes

After: unpublished commit is preserved and the client warns

After: unpublished commit remains checked out with the off-head warning

Remaining limits

  • Human approval is still needed for the production checkout/branch behavior flagged by Macroscope. This PR is unmerged.
  • Live GitLab, Azure DevOps, Bitbucket, Forgejo and GitCafe accounts were unavailable; macOS, Windows, Electron desktop, iOS and Android were not exercised.
  • @saphid: Azure's lastMergeSourceCommit can lag a new push and produce a conservative warning; live timing verification remains useful. An unrelated untracked file can prevent the extra refresh on switch-only hosts, preserving the branch and file and returning the warning. External terminals and other server processes are outside the process-local lease.

Tests: GPT-6-Astra; implementation: GPT-6.1-Sol; reviews: Claude Opus 5.5 and GPT-6-Sol. Work coordinated through T3 Code with the Codex harness (Claude harness for Opus review).

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Oct 3, 2026
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Oct 3, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — This PR changes production checkout behavior across Git workflows, provider adapters, and the web UI, including an autonomous tracked-change gate, lossless branch refresh, and shared concurrency leases. The focused tests are substantial, but the breadth and user-data-preservation impact require explicit validation of branch and provider compatibility.

No code changes detected at 6d1dab8. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: da2cb004-6766-4ce1-b4ee-4a735ba49e8b
📥 Commits

Reviewing files that changed from the base of the PR and between 32855a9 and 807a2af.

📒 Files selected for processing: 10
  • apps/server/src/git/GitManager.test.ts
  • apps/server/src/git/GitManager.ts
  • apps/server/src/git/GitWorkflowService.ts
  • apps/web/src/components/PullRequestThreadDialog.test.ts
  • apps/web/src/components/PullRequestThreadDialog.tsx
  • apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx
  • apps/web/src/components/pullRequest/pullRequestCheckoutToast.ts
  • packages/source-control-azure-devops/src/server/AzureDevOpsSourceControlProvider.ts
  • packages/source-control-bitbucket/src/server/BitbucketSourceControlProvider.test.ts
  • packages/source-control-forgejo/src/server/ForgejoSourceControlProvider.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.

📝 Walkthrough

Local pull request checkout now rejects tracked changes, avoids forced checkout, and checks whether the resulting branch matches the provider-reported head SHA. Four providers supply that SHA when available. Selected Git operations use workspace leases. The UI shows a shared warning when the head is not confirmed.

Changes: Pull request checkout

Layer / File(s) Summary
Propagate provider head SHAs
packages/source-control-azure-devops/src/server/*, packages/source-control-bitbucket/src/server/*, packages/source-control-forgejo/src/server/*, packages/source-control-gitlab/src/server/*
The four providers normalize available pull request head commit identifiers and include them in change requests. Tests cover parsing and mapping.
Lease Git workflow operations
apps/server/src/git/GitWorkflowService.ts, apps/server/src/git/GitWorkflowService.test.ts
GitWorkflowService resolves checkout roots and leases selected branch operations. Its tests now run with the Node services layer.
Protect and verify local checkout
apps/server/src/git/GitManager.ts, apps/server/src/git/GitManager.test.ts
Local preparation refuses tracked changes and checks out without force. GitManager attempts to refresh to the reported head and returns whether the checkout matches it. Tests cover concurrent workflows, dirty and diverged branches, branch creation and updates, and unavailable head metadata.
Show the shared off-head warning
apps/web/src/components/PullRequestThreadDialog.tsx, apps/web/src/components/PullRequestThreadDialog.test.ts, apps/web/src/components/pullRequest/pullRequestCheckoutToast.ts, apps/web/src/components/pullRequest/usePullRequestActions.ts, apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx
The thread dialog, pull request actions, and detail panel use a shared warning when the checkout is not confirmed on the head.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant PullRequestUI
  participant GitManager
  participant SourceControlProvider
  participant Git
  participant toastManager
  PullRequestUI->>GitManager: Prepare local pull request checkout
  GitManager->>Git: Check tracked changes
  GitManager->>SourceControlProvider: Request checkout without force
  GitManager->>Git: Resolve reported head SHA and refresh checkout
  Git-->>GitManager: Return head match status
  GitManager-->>PullRequestUI: Return preparation result
  PullRequestUI->>toastManager: Show warning when head is not confirmed
Loading

Possibly related PRs

  • pingdotgg/t3code#14849: Adds headSha to ChangeRequest and carries provider-reported pull request heads into GitManager, which this PR uses during local checkout.

Suggested reviewers: juliusmarminge

Merge Risk: ⚪ Minimal · up to 807a2

Local pull request checkout now refuses tracked edits, no longer forces checkout and warns when it cannot confirm the pull request head. Provider metadata still maps correctly, and no concrete merge-blocking defect was found. Live multi-provider and client testing remains limited, as the author notes.

Security architecture risk: 🔵 Low · up to 807a2

The change strengthens protection of local edits and unpublished commits without adding new permissions. Remaining risk concerns provider-specific checkout behavior and coordination that covers only participating operations in one server process.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The reviewed transition acts on the selected local Git checkout and its branch/upstream configuration through existing provider integrations. The per-root lease is concurrency control, not tenant isolation: its in-memory registry cannot serialize external terminals or another server process, and only explicitly wrapped workflows participate.

Trust Boundaries and Controls

  • observed — Provider-reported commit text crosses into local Git verification only after a 7–64 hexadecimal-character check, followed by resolution as a commit object. This excludes command-option and revision-expression text from the new hash input. Missing, malformed, unresolved, or failed verification produces false. Confirmation compares against the provider-reported snapshot, not a continuously current remote head.
  • observed — The inspected UI changes preserve existing action and environment-scope checks. The dialog adds an unconfirmed-head warning, while the detail panel replaces its previous warning text with the shared warning; neither change adds a new checkout or task-execution permission.

Resilience and Maintainability Implications

  • observed — Canonical real-path resolution makes nested projects and path aliases share a checkout-root lease when resolution succeeds. Resolution failure falls back to the supplied root path, so alias equivalence is not guaranteed in that fallback state. This is a limit of the added coordination rather than an established new attack path.
Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly describes the main change: preventing local pull-request checkout from discarding local work. It is concise and uses the repository’s conventional commit format.
Linked Issues check Passed Issue [#14940] requires local checkout to protect tracked edits and unpublished commits while preserving clean checkout behavior. GitManager.preparePullRequestThread checks `git status --porcelain -…
Out of Scope Changes check Passed The changes stay connected to [#14940]. Provider head metadata and warning UI implement the issue's later maintainer-triage requirement. The canonical checkout-root lease protects the local checkout d…
Description check Passed The description provides the problem, implementation details, scope context, verification results, screenshots, limitations, and agent attribution. It does not use the template’s explicit Problem, Cha…
Full details: Linked Issues check

Explanation

Issue [#14940] requires local checkout to protect tracked edits and unpublished commits while preserving clean checkout behavior. GitManager.preparePullRequestThread checks git status --porcelain --untracked-files=no before the provider call and no longer passes force. It uses the existing lossless refresh to move a switch-only checkout toward the provider head, and it reports isOnPullRequestHead from the resulting commit. The PR also implements the later head-confirmation requirement with provider headSha mappings and shared warnings in both web entry points. The reported focused tests cover dirty refusal, divergent and ahead branches, clean refresh, unavailable heads, and checkout concurrency.

Full details: Out of Scope Changes check

Explanation

The changes stay connected to [#14940]. Provider head metadata and warning UI implement the issue's later maintainer-triage requirement. The canonical checkout-root lease protects the local checkout during preparation and competing branch mutations. Related provider mappings, tests, and worktree-preserving behavior support this checkout safety objective. No demonstrated change lacks a connection to the linked issue.

Full details: Description check

Explanation

The description provides the problem, implementation details, scope context, verification results, screenshots, limitations, and agent attribution. It does not use the template’s explicit Problem, Change, or Scope and approval headings, and it links the maintained PR rather than directly linking the triaged issue, but the required information is present and the description is substantially complete.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@saphid
saphid force-pushed the upstream-pr-checkout-keep-local-work branch from 6feee5a to 0fc9753 Compare October 4, 2026 10:03
@macroscopeapp
macroscopeapp Bot dismissed their stale review October 4, 2026 10:03

Dismissing prior approval to re-evaluate 0fc9753

@saphid

saphid commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

Replying to the CodeRabbit and Macroscope summaries after the latest push:

  • Stale head reported as success (CodeRabbit retained concern): Addressed in the latest push. Local checkout now derives isOnPullRequestHead from the resulting HEAD. It resolves the pull request head the same way worktree reuse already does (refs/pull/<n>/head, falling back to the head branch's upstream). It reads that ref only from a remote verified to point at the pull request's repository (an SSH remote is matched by the host ssh -G resolves for it, the way gh resolves host aliases, so an alias matches and a Host github.com entry pointing at another server doesn't; GitHub's SSH-over-port-443 host counts as github.com; a custom core.sshCommand config isn't read). The head-branch fallback is used only from the head repository's own remote. If neither can be verified, checkout reports that it is not on the head rather than guessing. A branch that is kept ahead or stale now gets the existing "Checked out, but not on the latest commits" warning instead of success. That holds from both places that check out into the current repository: the pull request panel and the branch picker's Checkout pull request → Local. Regression tests cover the ahead branch, a fork and a local mirror publishing the same PR number, SSH host aliases, an SSH github.com redirected to another server, and GitHub's SSH-over-port-443 setup. The PR description shows both entry points in the real client with GitHub. Bitbucket and Forgejo are covered by the same server path but were not exercised live.

  • Status check vs. checkout not atomic (CodeRabbit trust boundaries): Right, the status check is a pre-flight. The race window is covered by checkout itself no longer being forced: an unforced gh pr checkout / git merge --ff-only refuses to overwrite a tracked edit that appears in between.

  • Docstring coverage warning: This repo doesn't use a docstring-coverage gate. The non-obvious new helper (resolvePullRequestHeadCommit) carries the comment explaining why it uses the pull request ref, and the tracked-change guard is self-describing.

@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). and removed size:S 10-29 changed lines (additions + deletions). labels Oct 4, 2026
@saphid

saphid commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor Author

Replying to the refreshed bot summaries on the latest push:

  • CodeRabbit "Out of Scope Changes" (head verification, SSH resolution, reused worktree, warning UI): the original issue text did scope those out, but the maintainer triage on #14940 widened the requirement: "isOnPullRequestHead should come from the resulting HEAD the way worktree reuse already does it." Deriving that flag needs the pull request's head commit, so local checkout now uses the resolver that worktree reuse already uses. The repository-identity checks (verified remote, SSH host resolution) exist only so that this resolver can't read another repository's ref with the same PR number. Without them the new flag could be wrong in the opposite direction. The dialog change is the branch picker's Local path showing the existing "not on the latest commits" warning, because it used to ignore the flag. No new UI was added.
  • Macroscope "needs human review" (breadth): understood; that's a reviewer's call. The change is confined to the PR checkout path (one guard, no --force, head derived from HEAD), and every identity rule has a real-Git regression test that fails without it (listed in the description).
  • CodeRabbit hardening note (shared FETCH_HEAD): fair, but pre-existing. Worktree reuse already fetched into FETCH_HEAD and resolved it in a separate step on main; this PR only reuses that path. A wrong read can't discard work, because checkout is unforced and reuse keeps its dirty-tree and local-commit guards. In the worst case, a clean reused worktree could fast-forward to the wrong commit, as it could on main. Per-working-copy serialization would be a separate change.
  • Docstring coverage: answered above. This repo has no docstring gate.

@saphid

saphid commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Review requested

Date (UTC) Reviewer Where
2026-10-03 Julius Discord DM

Logged so this PR shows when a maintainer was asked to review it.

@saphid

saphid commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Review requested

Date (UTC) Reviewer Where
2026-10-06 Julius Discord DM

Logged so this PR shows when a maintainer was asked to review it.

saphid and others added 3 commits October 14, 2026 02:45
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ead commit

Narrow the local checkout fix to what the data-loss bug needs. Local
checkout still refuses tracked edits and no longer forces the provider
checkout, but whether the result is on the pull request's head is now
read by comparing HEAD with the head commit the provider already
reported, instead of fetching refs from a remote matched through SSH
host resolution.

- Drop the SSH alias/port-443 remote identity matching, the per-remote
  head fetch and `GitVcsDriver.resolveSshHostName`; the reused-worktree
  refresh goes back to main's behaviour.
- GitLab, Bitbucket, Forgejo and Azure DevOps now pass through the head
  commit their reads already return, so a checkout on those hosts is
  not reported off-head just for lacking one.
- Share the off-head warning from a small module, worded neutrally
  since a branch kept ahead is not older.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without `force`, Bitbucket, Forgejo and GitCafe check out an existing
pull request branch as it was, so a branch behind the pull request
stayed there. After the provider checkout, the branch now moves to the
provider's head commit through the same clean, fast-forward-only
refresh reused worktrees use; diverged or dirty checkouts stay put and
report that they are not on the head.

The warning now says the checkout could not be confirmed on the latest
commit rather than asserting that its code differs, since a provider
that reports no head commit also lands there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@saphid

saphid commented Oct 14, 2026

Copy link
Copy Markdown
Contributor Author

Narrowed and updated at 21683f2 (current main merged in). Against the triage on #14940:

  • No hard reset: local mode no longer passes force.
  • Unpublished commits and tracked edits preserved: tracked edits are refused before the provider runs; a diverged branch is refused by gh and kept; an ahead branch is kept.
  • Clean fast-forward and first checkout still work: gh fast-forwards itself; for Bitbucket, Forgejo and GitCafe, whose unforced checkout only switches to the existing branch, the branch is now fast-forwarded to the provider's head commit through the existing refreshCheckedOutBranch step (never a reset).
  • Flag from the resulting HEAD: isOnPullRequestHead compares HEAD with the head commit the provider already returns (ChangeRequest.headSha, now passed through by GitLab, Bitbucket, Forgejo and Azure too); no head commit means "not confirmed", never an unconditional true.

On the review notes:

  • Macroscope (breadth): agreed. The SSH host resolution (ssh -G), per-remote head fetch and the reused-worktree changes are removed; using the provider's head commit makes them unnecessary. Worktree reuse is back to main's behaviour.
  • CodeRabbit: the 80% docstring threshold is not a repository rule, so no docstrings were added for their own sake. The FETCH_HEAD concern applied to the extra local-mode fetch, which no longer exists.

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Oct 14, 2026
… discarding local pull request work

Preserve the scoped contribution against upstream 70d60e0.
Focused checks and read-only integration review recorded in the PR.

GPT-6.1 Sol (high), Codex harness through T3 Code.
@macroscopeapp
macroscopeapp Bot dismissed their stale review October 17, 2026 02:57

Dismissing prior approval to re-evaluate cb474b0

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Oct 17, 2026
Use the existing workspace lease for the local checkout transaction and competing server branch operations. Canonicalize checkout roots consistently and guard the captured branch before refreshing.

Verified with 165 focused tests, server typecheck, targeted lint/formatting and precommit. Frozen-tree review: GPT-6.1 Sol high, Codex through T3 Code.
@macroscopeapp
macroscopeapp Bot dismissed their stale review October 17, 2026 03:44

Dismissing prior approval to re-evaluate 32855a9

@saphid

saphid commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

Published the checkout concurrency repair in 32855a9d03.

Another client's branch switch could run after the PR branch was captured and before its final fast-forward, causing the refresh to advance the newly selected branch. Local PR preparation now holds the existing workspace lease through refusal, checkout, upstream setup, branch guard and refresh. Switch, create-and-switch and feature-branch actions share that lease using the same canonical checkout-root policy, including projects rooted in subdirectories. External terminal Git and other server processes remain outside this process-local coordination.

Verification: 165 focused GitManager/GitWorkflowService tests passed, server typecheck and targeted lint/formatting/precommit passed. Seven real-Git barrier cases cover independent workflows and alternate root spelling; the regression cases fail before their respective repairs. This is service-boundary proof, not a Windows or live two-socket UI run. Fresh read-only GPT-6.1 Sol high review through T3 completed with no actionable findings on the exact published tree. It was a same-model independent review because Claude quota is unavailable.

The description now reflects the provider-head metadata implementation and current scope under the maintainer triage. Earlier UI evidence retains its recorded revision and limitations. New-head CI and bot review are pending.

@juliusmarminge juliusmarminge self-assigned this Oct 19, 2026
@juliusmarminge

Copy link
Copy Markdown
Member

Taking over this one as maintainer for batch pull-request-13. I will preserve local work during checkout, merge current main, reduce the test surface, rebuild against current conventions, and run independent reviews. This will remain unmerged.

@juliusmarminge
juliusmarminge merged commit 23b550d into pingdotgg:main Oct 20, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants